/*
 * Copyright (c) 2012 OptiCare Systems Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * Authors: Kevin Martin (kev82@khn.org.uk)
 */
#include <lua5.2/lua.hpp>
#include <lua5.2/lauxlib.h>
#include <map>
#include <string>
#include <stdexcept>
#include <cstdlib>
#include <sstream>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include <vector>
#include <cstring>
#include <utility>
#include "connect.hpp"

extern "C" {
	int luaopen_curleasy(lua_State *l);
}

static bool mt_curl_check() {
#ifdef OPTICARE_INTERNAL_BUILD
	return CurlInit::BoolCheck();
#else
	std::cout << "curleasy: This will not work in threaded programs (even if it appears to).\n";
	std::cout << "To use it in threaded programs you must figure a way to initialize\n";
	std::cout << "curl before the threads launch and then disable this warning";
	std::cout << std::endl;
	sleep(5);
	return true;
#endif
}

static int table2Map(lua_State *l, int tblidx,
 MSS &m) {
	tblidx = lua_absindex(l, tblidx);

	if(!lua_istable(l, tblidx)) {
        luaL_error(l, "Expexcted Table");
    }

    lua_pushnil(l);
    while (lua_next(l, tblidx) != 0) {
        if(!lua_isstring(l, -2)) {
            luaL_error(l, "Table has integer key");
        }
        if(!lua_isstring(l, -1)) {
            luaL_error(l, "Table has non-string value");
        }

        m[lua_tostring(l, -2)] = lua_tostring(l, -1);

        lua_pop(l, 1);
    }
}

static int map2table(const MSS &m, lua_State *l) {
	lua_createtable(l, 0, m.size());
	for(MSS::const_iterator i=m.begin();i!=m.end();++i) {
		lua_pushstring(l, i->first.c_str());
		lua_pushstring(l, i->second.c_str());
		lua_settable(l, -3);
	}
}
	
static const char *metatable_uuid = "0cf0e8dc-b8bb-496f-a7a8-2d2a3c85d441";

static int api_new_connector(lua_State *l) {
	if(lua_gettop(l) != 0) {
		luaL_error(l, "Expected no arguments");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)lua_newuserdata(l, sizeof(ConnectionOptions *));

	*ppco = new ConnectionOptions;

	lua_pushstring(l, metatable_uuid);
	lua_gettable(l, LUA_REGISTRYINDEX);
	if(lua_type(l, -1) != LUA_TTABLE) {
		luaL_error(l, "Internal error - metatable doesn't exist");
	}
	lua_setmetatable(l, -2);

	return 1;
}

static int int_co_optionsfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	luaL_checktype(l, 2, LUA_TTABLE);

	MSS temp;
	table2Map(l, 2, temp);
	for(MSS::const_iterator i=temp.begin();i!=temp.end();++i) {
		if(i->first == "verbose") {
			co.beVerbose = true;
		} else if(i->first == "addheaders") {
			co.addHeaders = true;
		} else if(i->first == "no_body") {
			co.noBody = true;
		} else {
			luaL_error(l, "unknown option %s", i->first.c_str());
		}
	}

	return 0;
}

static int int_co_urlfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	co.URL = luaL_checkstring(l, 2);

	return 0;
}

static int int_co_getargsfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	luaL_checktype(l, 2, LUA_TTABLE);

	table2Map(l, 2, co.getdatamap);
	
	return 0;
}

static int int_co_putfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	co.doput = true;
	size_t datalen;
	const char *data;
	data = lua_tolstring(l, 2, &datalen);
	co.putdata.resize(datalen);
	std::copy(data, data+datalen, &co.putdata[0]);

	return 0;
}

static int int_co_postfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	luaL_checktype(l, 2, LUA_TTABLE);

	co.dopost = true;
	table2Map(l, 2, co.postdatamap);
	
	return 0;
}

static int int_co_headersfunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	luaL_checktype(l, 2, LUA_TTABLE);

	table2Map(l, 2, co.headermap);
	
	return 0;
}

static int int_co_retrievefunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 2 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	std::string thing = luaL_checkstring(l, 2);
	if(thing == "headers") {
		map2table(co.headermap, l);
	} else if(thing == "url") {
		lua_pushstring(l, co.URL.c_str());
	} else {
		luaL_error(l, "Unknown thing to retrieve");
	}
	
	return 1;
}

static int int_co_deletefunc(lua_State *l) {
	if(lua_gettop(l) != 2) {
		luaL_error(l, "Expected 1 parameter");
	}

	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;
	
	co.dodel = true;
	
	return 0;
}

static int int_delete_co(lua_State *l) {
	assert(lua_gettop(l) == 1);
	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	delete (*ppco);
	return 0;
}

static int int_comt_index(lua_State *l) {
	assert(lua_gettop(l) == 2);
	luaL_checkudata(l, 1, metatable_uuid);
	std::string idx = luaL_checkstring(l, 2);

	if(idx == "options") {
		lua_pushcfunction(l, int_co_optionsfunc);
	} else if(idx == "url") {
		lua_pushcfunction(l, int_co_urlfunc);
	} else if(idx == "getargs") {
		lua_pushcfunction(l, int_co_getargsfunc);
	} else if(idx == "put") {
		lua_pushcfunction(l, int_co_putfunc);
	} else if(idx == "post") {
		lua_pushcfunction(l, int_co_postfunc);
	} else if(idx == "delete") {
		lua_pushcfunction(l, int_co_deletefunc);
	} else if(idx == "headers") {
		lua_pushcfunction(l, int_co_headersfunc);
	} else if(idx == "retrieve") {
		lua_pushcfunction(l, int_co_retrievefunc);
	} else {
		luaL_error(l, "Can't index curleasy object");
	}

	return 1;
}

static int int_comt_call(lua_State *l) {
	ConnectionOptions **ppco =
	 (ConnectionOptions **)luaL_checkudata(l, 1, metatable_uuid);

	ConnectionOptions &co = **ppco;

	try {
		std::vector<char> rv = CurlConnect(co);
		lua_pushlstring(l, &rv[0], rv.size());
		return 1;
	} catch(std::exception &e) {
		luaL_error(l, e.what());
	}
}

static const luaL_Reg metatable_funcs[] = {
	{"__gc", int_delete_co},
	{"__index", int_comt_index},
	{"__call", int_comt_call},
	{NULL, NULL}
};

static const luaL_Reg curleasy_funcs[] = {
	{"connector", api_new_connector},
	{NULL, NULL}
};

int luaopen_curleasy(lua_State *l) {
#ifndef CURLEASY_DEFINITELY_SINGLE_THREADED
	if(!mt_curl_check()) {
		luaL_error(l, "Curl has not been initialized");
	}
#endif

	lua_newtable(l);
	luaL_setfuncs(l, metatable_funcs, 0);
	lua_pushstring(l, metatable_uuid);
	lua_insert(l, -2);
	lua_settable(l, LUA_REGISTRYINDEX);
	
	luaL_newlib(l, curleasy_funcs);
	return 1;
}
